/*
 * Amaze - QT downloader for the Amazon MP3 store
 * Copyright (c) 2010-2011 Robert Keevil
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <QFileDialog>
#include <QFileInfo>
#include <QDir>
#include <QMessageBox>
#include "amaze.h"
#include "settings.h"
#include "parse.h"
#include "download.h"
#include "console.h"

Amaze::Amaze(QWidget *parent) :
    QMainWindow(parent)
{
    config = new Config();
    tracks = new QList<track>;
    log_messages = new QList<log_entry>;

    add_log(LOG_INFO, QString("%1 v%2 starting up")
            .arg(AMAZE_NAME).arg(AMAZE_VERSION));

    aa_request.setRawHeader("User-Agent", QString("%1 v%2")
                         .arg(AMAZE_NAME)
                         .arg(AMAZE_VERSION)
                         .toAscii());

    aa_manager = new QNetworkAccessManager();
    aa_reply = NULL;

    mutex = new QMutex();
    about_dialog = NULL;
    settings_dialog = NULL;
    console_dialog = NULL;
    format_dialog = NULL;
    license_dialog = NULL;
    parser = NULL;
    dl = new Download(this);

    create_folders();

    setup_window();

    if (QCoreApplication::arguments().size() > 1)
        open(QCoreApplication::arguments().at(1));
}

Amaze::~Amaze()
{
    config->save_config();
}

void Amaze::create_folders()
{
    // Create backup folder
    QString backup_path = QString("%1/.amaze").arg(QDir::homePath());
    QDir dir_make;
    if (!dir_make.mkpath(backup_path))
    {
        QMessageBox::warning(this, AMAZE_NAME,
                              tr("Unable to create backup folder %1. "\
                                 "%2 will be unable to create backups "\
                                 "of your downloaded .amz files.")
                              .arg(backup_path)
                              .arg(AMAZE_NAME));
    }
}

void Amaze::setup_window()
{
    this->resize(800, 600);
    setup_widgets();
    setup_connects();
}

void Amaze::setup_widgets()
{
    track_table = new QTableWidget();
    table_labels.clear();
    // keep synced with the enum in amaze.h
    table_labels << tr("Artist") << tr("Album") << tr("Title")
            << tr("TrackNum") << tr("Status") << tr("Progress");
    track_table->setColumnCount(COL_NUM_COL);
    track_table->setRowCount(0);
    track_table->setHorizontalHeaderLabels(table_labels);
    track_table->setAlternatingRowColors(true);
    track_table->setSelectionBehavior(QAbstractItemView::SelectRows);
    track_table->horizontalHeader()->setResizeMode(QHeaderView::Stretch);

    act_exit        = new QAction(tr("Exit"),        this);
    act_about       = new QAction(tr("About"),       this);
    act_about_qt    = new QAction(tr("AboutQt"),     this);
    act_settings    = new QAction(tr("Settings"),    this);
    act_console     = new QAction(tr("Console"),     this);
    act_open        = new QAction(tr("Open"),        this);
    act_format_help = new QAction(tr("Format Help"), this);
    act_license     = new QAction(tr("License"),     this);

    central_widget  = new QWidget(this);

    btn_open        = new QPushButton(tr("Open"));
    btn_download    = new QPushButton(tr("Download"));

    btn_open->setIcon(QIcon::fromTheme("document-open",
                       QIcon(":/resources/icons/document-open.png")));
    btn_download->setIcon(QIcon(":/resources/icons/16x16/amaze.png"));


    /* Bottom frame:
             0            1          2        3          4
      |------------|-------------|-------|----------|----------------------|
    0 |            | AlbumArtist | data  | Genre    | data                 |
    1 |   QLabel   | Artist      | data  | Length   | data                 |
    2 |     AA     | Album       | data  | Filesize | data                 |
    3 |            | Title       | data  |          |                      |
      |------------|-------------|-------|----------|----------------------|

       */

    lbl_albumartist  = new QLabel(tr("Album Artist:"));
    lbl_artist       = new QLabel(tr("Artist:"));
    lbl_album        = new QLabel(tr("Album:"));
    lbl_title        = new QLabel(tr("Title:"));
    lbl_genre        = new QLabel(tr("Genre:"));
    lbl_length       = new QLabel(tr("Length:"));
    lbl_filesize     = new QLabel(tr("Filesize:"));

    info_albumartist = new QLabel();
    info_artist      = new QLabel();
    info_album       = new QLabel();
    info_title       = new QLabel();
    info_genre       = new QLabel();
    info_length      = new QLabel();
    info_filesize    = new QLabel();

    btm_frame = new QFrame();
    btm_frame->setFrameStyle(QFrame::Panel | QFrame::Sunken);
    btm_aa = new QLabel();
    btm_aa->setFixedSize(75, 75);
    QGridLayout *btm_layout = new QGridLayout(btm_frame);

    btm_layout->addWidget(btm_aa, 0, 0, 4, 1);
    btm_layout->addWidget(lbl_albumartist,  0, 1);
    btm_layout->addWidget(lbl_artist,       1, 1);
    btm_layout->addWidget(lbl_album,        2, 1);
    btm_layout->addWidget(lbl_title,        3, 1);

    btm_layout->addWidget(info_albumartist, 0, 2);
    btm_layout->addWidget(info_artist,      1, 2);
    btm_layout->addWidget(info_album,       2, 2);
    btm_layout->addWidget(info_title,       3, 2);

    btm_layout->addWidget(lbl_genre,        0, 3);
    btm_layout->addWidget(lbl_length,       1, 3);
    btm_layout->addWidget(lbl_filesize,     2, 3);

    btm_layout->addWidget(info_genre,       0, 4);
    btm_layout->addWidget(info_length,      1, 4);
    btm_layout->addWidget(info_filesize,    2, 4);

    btm_layout->setColumnStretch(2, 1);
    btm_layout->setColumnStretch(4, 1);

    /* Layout:
                  0                1             2               3
      |--------------------------------------------------------------------|
      |                    |     open     |  download  |                   |
      |--------------------------------------------------------------------|
      |                                                                    |
      |                           QTableWidget                             |
      |                                                                    |
      |--------------------------------------------------------------------|
      |                             QFrame                                 |
      |--------------------------------------------------------------------|
      |--QStatusBar--------------------------------------------------------|

      */

    QGridLayout *layout = new QGridLayout(central_widget);
    layout->setMargin(0);
    layout->setColumnStretch(0, 1);
    layout->addWidget(btn_open, 0, 1);
    layout->addWidget(btn_download, 0, 2);
    layout->setColumnStretch(3, 1);
    layout->addWidget(track_table, 1, 0, 1, 4);
    layout->addWidget(btm_frame, 2, 0, 1, 4);

    this->setCentralWidget(central_widget);

    menu_bar = new QMenuBar(this);
    menu_file = new QMenu(tr("File"),menu_bar);
    menu_help = new QMenu(tr("Help"),menu_bar);
    this->setMenuBar(menu_bar);
    statusbar = new QStatusBar(this);
    this->setStatusBar(statusbar);

    lbl_status = new QLabel(tr("Ready"));
    statusbar->addWidget(lbl_status, 1);

    act_settings->setIcon(QIcon::fromTheme("preferences-other",
                           QIcon(":/resources/icons/preferences-other.png")));
    act_exit->setIcon(QIcon::fromTheme("application-exit",
                           QIcon(":/resources/icons/application-exit.png")));
    act_open->setIcon(QIcon::fromTheme("document-open",
                           QIcon(":/resources/icons/document-open.png")));
    act_about->setIcon(QIcon::fromTheme("help-about",
                           QIcon(":/resources/icons/help-about.png")));

    menu_bar->addAction(menu_file->menuAction());
    menu_bar->addAction(menu_help->menuAction());

    menu_file->addAction(act_open);
    menu_file->addAction(act_settings);
    menu_file->addSeparator();
    menu_file->addAction(act_exit);

    menu_help->addAction(act_console);
    menu_help->addAction(act_format_help);
    menu_help->addSeparator();
    menu_help->addAction(act_license);
    menu_help->addAction(act_about);
    menu_help->addAction(act_about_qt);

    this->setWindowTitle(AMAZE_NAME);

    // call the fontmetric magic
    update_table();
}


void Amaze::setup_connects()
{
    connect(track_table,     SIGNAL(itemSelectionChanged()),
                             this, SLOT(update_panel()));
    connect(act_about,       SIGNAL(triggered()), this, SLOT(about()));
    connect(act_about_qt,    SIGNAL(triggered()), this, SLOT(about_qt()));
    connect(act_console,     SIGNAL(triggered()), this, SLOT(console()));
    connect(act_format_help, SIGNAL(triggered()), this, SLOT(format()));
    connect(act_license,     SIGNAL(triggered()), this, SLOT(license()));
    connect(act_exit,        SIGNAL(triggered()), this, SLOT(close()));
    connect(act_settings,    SIGNAL(triggered()), this, SLOT(settings()));
    connect(act_open,        SIGNAL(triggered()), this, SLOT(open()));
    connect(btn_open,        SIGNAL(clicked()),   this, SLOT(open()));
    connect(btn_download,    SIGNAL(clicked()),   this, SLOT(download()));
    connect(dl,              SIGNAL(finished(int, bool, QString)),
                             this, SLOT(download_finished(int, bool, QString)));
    connect(dl,              SIGNAL(add_log(LOG_LEVEL,QString)),
                             this, SLOT(add_log(LOG_LEVEL,QString)));
    connect(dl,              SIGNAL(started(int)), this, SLOT(dl_started(int)));
    connect(dl,              SIGNAL(progress(int,qint64,qint64)),
                             this, SLOT(dl_progress(int,qint64,qint64)));
    connect(aa_manager,      SIGNAL(finished(QNetworkReply*)),
                             this, SLOT(aa_get_finished()));
}

void Amaze::about()
{
    if (about_dialog == NULL)
        about_dialog = new About();
    about_dialog->show();
}

void Amaze::about_qt()
{
    QApplication::aboutQt();
}

void Amaze::console()
{
    if (console_dialog == NULL)
        console_dialog = new Console(this);
    console_dialog->show();
}

void Amaze::dl_started(int track_index)
{
    track tmp = tracks->at(track_index);
    tmp.state = TRACK_DOWNLOADING;
    tracks->replace(track_index, tmp);
    update_status();
    track_table->setCellWidget(track_index, COL_PROGRESS, new QProgressBar());
}

void Amaze::dl_progress(int track_index, qint64 bytes_current, qint64 bytes_total)
{
    QProgressBar *progress = (QProgressBar *)track_table->cellWidget(track_index, COL_PROGRESS);
    progress->setMaximum(bytes_total);
    progress->setValue(bytes_current);
}

void Amaze::download()
{
    lbl_status->setText(tr("Downloading tracks"));
    //reset any failed downloads
    for (int i = 0; i < tracks->size(); i++)
    {
        track tmp = tracks->at(i);
        if (tmp.state != TRACK_SUCCESS)
        {
            tmp.state = TRACK_NEW;
            tracks->replace(i, tmp);
        }
    }

    dl->get_next();
}

void Amaze::download_finished(int track_index, bool success, QString error_msg)
{
    add_log(success?LOG_INFO:LOG_ERROR, error_msg);
    track tmp = tracks->at(track_index);
    if (success)
    {
        tmp.state = TRACK_SUCCESS;
        dl->get_next();
    }
    else
    {
        tmp.state = TRACK_ERROR;
        QMessageBox::critical(this, AMAZE_NAME,
                              tr("Download failed: %1").arg(error_msg),
                             QMessageBox::Discard);
    }
    tracks->replace(track_index, tmp);
    update_status();
    bool done = true;
    for (int i = 0; i < tracks->size(); i++)
    {
        if (tracks->value(i).state == TRACK_NEW)
            done = false;
    }
    if (done)
        lbl_status->setText(tr("Finished downloading"));
}

void Amaze::format()
{
    if (format_dialog == NULL)
        format_dialog = new Format_Help();
    format_dialog->show();
}

void Amaze::license()
{
    if (license_dialog == NULL)
        license_dialog = new License();
    license_dialog->show();
}

void Amaze::open()
{
    QString file = QFileDialog::getOpenFileName(this, tr("Open File"),
                                                     QDir::homePath(),
                                                     tr("Amazon MP3 Download Queue (*.amz)"));
    open(file);
}

void Amaze::open(QString filepath)
{
    if (parser == NULL)
    {
        parser = new Parse(this);
        connect(parser, SIGNAL(parsed()), this, SLOT(parsed()));
        connect(parser, SIGNAL(add_log(LOG_LEVEL,QString)),
                this, SLOT(add_log(LOG_LEVEL,QString)));
    }

    if (!filepath.isEmpty())
    {

        target = new QFile(filepath);

        if (target->exists())
        {
            // create a backup of the .amz file, just incase
            QFileInfo info(filepath);
            QString filename = info.fileName();
            QString backup = QString("%1/.amaze/%3")
                             .arg(QDir::homePath())
                             .arg(filename);

            if (!QFile(backup).exists() && !QFile::copy(filepath, backup))
            {
                QMessageBox::warning(this, AMAZE_NAME,
                                      tr("Unable to backup %1 to %2.")
                                      .arg(filename)
                                      .arg(backup));
            }

            parser->open(target);
        }
    }
}

void Amaze::parsed()
{
    lbl_status->setText(tr("Parsed %1 entries").arg(tracks->size()));
    make_paths();
    update_table();
}

void Amaze::make_paths()
{
    QMap<QString, QPixmap *>::const_iterator itr = aa_map.constBegin();
    while (itr != aa_map.constEnd()) {
        delete itr.value();
        ++itr;
    }
    aa_map.clear();

    for (int i = 0; i < tracks->size(); i++)
    {
        // create a path & filename from the requested format string and metadata
        track tmp = tracks->at(i);
        QString extension;
        if (tmp.track_type.isEmpty())
            extension = "mp3";
        else
            extension = tmp.track_type;
        QString path = QString("%1/%2.%3")
                       .arg(config->output_folder)
                       .arg(config->output_format)
                       .arg(extension);

        path.replace(AMAZE_ARTIST, tmp.artist);
        path.replace(AMAZE_ALBUM, tmp.album);
        path.replace(AMAZE_TITLE, tmp.title);
        path.replace(AMAZE_ALBUMARTIST, tmp.album_artist);
        path.replace(AMAZE_GENRE, tmp.genre);
        path.replace(AMAZE_DISCNUM, tmp.discnum);
        // give the track number a leading zero
        path.replace(AMAZE_TRACKNUM, tmp.tracknum.rightJustified(2, '0'));
        tmp.filepath = path;
        tracks->replace(i, tmp);

        if (!tmp.image_url.isEmpty())
            aa_map.insert(tmp.image_url, new QPixmap());
    }
}

void Amaze::settings()
{
    if (settings_dialog == NULL)
        settings_dialog = new Settings(this);
    connect(settings_dialog, SIGNAL(close()), this, SLOT(settings_close()));
    settings_dialog->show();
}

void Amaze::settings_close()
{
    // delete the settings widget, to ensure that cancelled changes don't reappear
    settings_dialog->deleteLater();
}

void Amaze::update_table()
{
    track tmp;
    track_table->clearContents();

    track_table->setRowCount(tracks->count());

    QApplication::setOverrideCursor(Qt::WaitCursor);

    // add each track to the table, in a 1:1 mapping (allows download progress
    // updates to easily be applied to the correct track in the table
    for (int i = 0; i < tracks->count(); i++)
    {
        tmp = tracks->at(i);

        QTableWidgetItem *temp_artist = new QTableWidgetItem;
        temp_artist->setText(tmp.artist);
        temp_artist->setFlags(temp_artist->flags() &~ Qt::ItemIsEditable);
        track_table->setItem(i, COL_ARTIST, temp_artist );

        QTableWidgetItem *temp_album = new QTableWidgetItem;
        temp_album->setText(tmp.album);
        temp_album->setFlags(temp_album->flags() &~ Qt::ItemIsEditable);
        track_table->setItem(i, COL_ALBUM, temp_album );

        QTableWidgetItem *temp_title = new QTableWidgetItem;
        temp_title->setText(tmp.title);
        temp_title->setFlags(temp_title->flags() &~ Qt::ItemIsEditable);
        track_table->setItem(i, COL_TITLE, temp_title );

        QTableWidgetItem *temp_tracknum = new QTableWidgetItem;
        temp_tracknum->setText(tmp.tracknum);
        temp_tracknum->setFlags(temp_tracknum->flags() &~ Qt::ItemIsEditable);
        track_table->setItem(i, COL_TRACKNUM, temp_tracknum );
    }

    QApplication::restoreOverrideCursor();
    update_status();
}

void Amaze::update_status()
{
    for (int i = 0; i < tracks->count(); i++)
    {
        QTableWidgetItem *temp;
        temp = new QTableWidgetItem;
        temp->setFlags(temp->flags() &~ Qt::ItemIsEditable);
        switch (tracks->at(i).state)
        {
        case TRACK_NEW:
            temp->setText(tr("Waiting"));
            break;

        case TRACK_DOWNLOADING:
            temp->setText(tr("Downloading"));
            break;

        case TRACK_SUCCESS:
            temp->setText(tr("Finished"));
            break;

        case TRACK_ERROR:
            temp->setText(tr("Error"));
            break;
        }
        track_table->setItem(i, COL_STATUS, temp);
    }
}

void Amaze::update_panel()
{
    int row = track_table->currentRow();

    // Check the user hasn't loaded a new .amz file with less entries
    // than the currently selected row
    if (row >= tracks->size())
    {
        // set blank
        btm_aa->clear();
        info_albumartist->clear();
        info_artist->clear();
        info_album->clear();
        info_title->clear();
        info_genre->clear();
        info_length->clear();
        info_filesize->clear();
        aa_url = "";
    }
    else
    {
        track tmp = tracks->at(row);
        info_albumartist->setText(tmp.album_artist);
        info_artist->setText(tmp.artist);
        info_album->setText(tmp.album);
        info_title->setText(tmp.title);
        info_genre->setText(tmp.genre);

        int len = tmp.duration.toInt();
        int min = len/60000;
        QString sec = QString::number((len/1000)%60).rightJustified(2, '0');
        info_length->setText(QString("%1:%2").arg(min).arg(sec));

        double filesize = (double)tmp.filesize / 1048576;
        info_filesize->setText(QString("%1 MB")
                               .arg(QString::number(filesize, 'f', 2)));

        aa_url = tmp.image_url;
        update_aa(row);
    }
}

void Amaze::update_aa(int index)
{
    track tmp = tracks->at(index);
    if (!tmp.image_url.isEmpty())
    {
        if (aa_map.value(tmp.image_url)->isNull())
        {
            // bail out if there is an active download
            if (aa_reply != NULL && !aa_reply->isFinished())
                return;

            aa_request.setUrl(QUrl(tmp.image_url));
            aa_reply = aa_manager->get(aa_request);
            aa_url_requested = tmp.image_url;
        }
        else
            btm_aa->setPixmap(*aa_map.value(tmp.image_url));

    }
}

void Amaze::aa_get_finished()
{
    if ( aa_reply->error() != QNetworkReply::NoError )
    {
        add_log(LOG_ERROR, tr("Failed to download AlbumArt: %2")
                     .arg(aa_reply->errorString()));
        return;
    }

    QUrl url = aa_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
    if (!url.isEmpty())
    {
        aa_request.setUrl(url);
        aa_reply = aa_manager->get(aa_request);
        emit add_log(LOG_DEBUG, QString("AA HTTP redirect to %1")
                     .arg(url.toString()));
    }
    else
    {
        QPixmap *pixmap = aa_map.value(aa_url_requested);
        pixmap->loadFromData(aa_reply->readAll());

        if (aa_url == aa_url_requested)
            btm_aa->setPixmap(*pixmap);
    }
}

void Amaze::add_log(LOG_LEVEL level, QString msg)
{
    log_entry tmp;
    tmp.level = level;
    tmp.msg = msg;
    log_messages->push_back(tmp);

    if (level <= log_print_level)
        QTextStream(stdout) << LOG_LEVEL_NAMES[level] << ": " << msg << endl;

    if (LOG_ERROR == level)
        error_str = msg;
}

void Amaze::clear_log()
{
    log_messages->clear();
}

void Amaze::set_log_level(LOG_LEVEL level)
{
    log_print_level = level;
}
